﻿using System;
using System.Data.Objects;
using System.Globalization;
using System.Threading;

namespace EFObjectContextScope
{
    public class ObjectContextWrapper<TObjectContext> : IDisposable
                                        where TObjectContext : ObjectContext
    {
        internal int cloneCount;
        internal int cloneId;
        internal int disposed;
        internal const int disposedTrueValue = 1;
        internal ObjectContextTraceIdentifier traceIdentifier;

        internal TObjectContext objectContext;

        private static string instanceIdentifier;
        private static object classSyncObject;

        internal ObjectContextWrapper()
        {
            this.cloneId = 1;
            this.cloneCount = 1;

            if (this.objectContext == null)
                this.objectContext = Activator.CreateInstance<TObjectContext>();
        }

        internal static void GetCurrentWrapperAndScope(
            out ObjectContextWrapper<TObjectContext> current,
            out ObjectContextScope<TObjectContext> currentScope,
            out ObjectContextData<TObjectContext> contextData,
            out ObjectContextWrapper<TObjectContext> contextObjectContextWrapper)
        {
            // Get Current Data
            contextData = ObjectContextData<TObjectContext>.CurrentData;

            currentScope = contextData.CurrentScope;
            current = RetrieveWrapper(contextData, out contextObjectContextWrapper);
        }

        internal static ObjectContextWrapper<TObjectContext> RetrieveWrapper(
            ObjectContextData<TObjectContext> contextData,
            out ObjectContextWrapper<TObjectContext> contextObjectContextWrapper)
        {
            contextObjectContextWrapper = null;
            contextObjectContextWrapper = contextData.CurrentObjectContextWrapper;
            return contextObjectContextWrapper;
        }

        internal static object ClassSyncObject
        {
            get
            {
                if (classSyncObject == null)
                {
                    object obj2 = new object();
                    Interlocked.CompareExchange(ref classSyncObject, obj2, null);
                }
                return classSyncObject;
            }
        }

        internal static string InstanceIdentifier
        {
            get
            {
                if (instanceIdentifier == null)
                {
                    lock (ClassSyncObject)
                    {
                        if (instanceIdentifier == null)
                        {
                            string str = Guid.NewGuid().ToString() + ":";
                            Thread.MemoryBarrier();
                            instanceIdentifier = str;
                        }
                    }
                }
                return instanceIdentifier;
            }
        }

        internal ObjectContextTraceIdentifier ObjectContextTraceId
        {
            get
            {
                if (this.traceIdentifier == ObjectContextTraceIdentifier.Empty)
                {
                    lock (this)
                    {
                        if (this.traceIdentifier == ObjectContextTraceIdentifier.Empty)
                        {
                            ObjectContextTraceIdentifier identifier = new ObjectContextTraceIdentifier(InstanceIdentifier + Thread.CurrentThread.Name + Convert.ToString(this.cloneId, CultureInfo.InvariantCulture), 0);
                            Thread.MemoryBarrier();
                            this.traceIdentifier = identifier;
                        }
                    }
                }
                return this.traceIdentifier;
            }
        }

        public override bool Equals(object obj)
        {
            ObjectContextWrapper<TObjectContext> wrapper = obj as ObjectContextWrapper<TObjectContext>;
            if (null == wrapper)
            {
                return false;
            }
            return (this.cloneId == wrapper.cloneId);
        }

        #region IDisposable Members

        /// <summary>
        /// Finalizes an instance of the <c>ObjectContextWrapper</c> class.
        /// <para>
        /// I have added this on to clean up unmanaged resources
        /// Microsoft recommend doing this even though unmanaged code should not be coming though this class.
        /// </para>
        /// <para>
        /// Do not re-create Dispose clean-up code here.
        /// Calling Dispose(false) is optimal in terms of
        /// readability and maintainability.
        /// </para>
        /// </summary>
        ~ObjectContextWrapper()
        {
            Dispose(false);
        }

        /// <summary>
        /// Implement <see cref="IDisposable"/>.
        /// Do not make this method virtual.
        /// A derived class should not be able to override this method.
        /// See: http://msdn.microsoft.com/en-us/library/system.idisposable.dispose.aspx
        /// </summary>
        public void Dispose()
        {
            Dispose(true);

            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue
            // and prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Dispose(<c>bool</c> disposing) executes in two distinct scenarios. If disposing
        /// equals true, the method has been called directly or indirectly by a user's
        /// code. Managed and unmanaged resources can be <see cref="disposed"/>. If
        /// disposing equals false, the method has been called by the runtime from inside
        /// the <c>finalizer</c> and you should not reference other objects. Only unmanaged
        /// resources can be <see cref="disposed"/>.
        /// </summary>
        /// <param name="disposing">Dispose of managed objects?</param>
        /// <exception cref="InvalidOperationException"><c>ObjectContextScope</c>
        /// cannot be disposed of if the parent scope is the same</exception>
        private void Dispose(bool disposing)
        {
            if (disposing)
                this.InternalDispose();
        }

        internal virtual void InternalDispose()
        {
            if (Interlocked.Exchange(ref this.disposed, 1) != 1)
            {
                //System.Diagnostics.Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, 
                //    "Calling internal Dispose for {0} (ObjectContextWrapper)",
                //                  ObjectContextTraceId.ObjectContextWrapperIdentifier));
                long num = Interlocked.Decrement(ref this.cloneCount);
                if (num == 0L)
                {
                    if (objectContext != null)
                    {
                        this.objectContext.Dispose();
                    }

                    instanceIdentifier = null;

                    //System.Diagnostics.Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, 
                    //    "Internal Dispose for {0} Completed (ObjectContextWrapper)",
                    //              ObjectContextTraceId.ObjectContextWrapperIdentifier));
                }
                else
                {
                    //System.Diagnostics.Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, 
                    //    "Internal Dispose for {0} not performed (ObjectContextWrapper)",
                    //              ObjectContextTraceId.ObjectContextWrapperIdentifier));
                }
            }
        }

        #endregion
    }
}
